package com.demo10;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.file.linux.AndroidFileIO;
import com.github.unidbg.linux.android.*;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.Enumeration;
import com.github.unidbg.linux.android.dvm.api.ServiceManager;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.linux.file.SimpleFileIO;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.unix.UnixSyscallHandler;


import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class Pdd extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass SecureNative;


    Pdd()  {
        AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
            public AndroidEmulator build() {
                return new AndroidARMEmulator(processName, rootDir,
                        backendFactories) {
                    @Override
                    protected UnixSyscallHandler<AndroidFileIO>
                    createSyscallHandler(SvcMemory svcMemory) {
                        return new PddARM32SyscallHandler(svcMemory);
                    }
                };
            }
        };

        emulator = builder.setRootDir(new File("target/rootfs")).build();

        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/demo10/apkfiles/拼多多官方.apk")); // 创建Android虚拟机

        SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
        systemPropertyHook.setPropertyProvider(key -> {
            System.out.println("fuckkey:"+key);
            switch (key){
                case "ro.build.version.sdk": {
                    return "23";
                }

                case "ro.serialno":{
                    return "010a29a00c61ae27";
                }

                case "ro.build.id":{
                    return "OPM7.181205.001";
                }

                case "ro.build.fingerprint":{
                    return "google/bullhead/bullhead:8.1.0/OPM7.181205.001/5080180:user/release-keys";
                }

                case "ro.build.characteristics":{
                    return "nosdcard";
                }

                case "sys.oem_unlock_allowed":{
                    return "0";
                }
            }
            return "";
        });
        memory.addHookListener(systemPropertyHook);

        emulator.getSyscallHandler().addIOResolver(this);
        emulator.getSyscallHandler().setVerbose(true);

        vm.setVerbose(true); // 设置是否打印Jni调用细节
        vm.setJni(this);

        new UserEnvModule(emulator).register(memory);
        DalvikModule dm = vm.loadLibrary("pdd_secure", true);
        module = dm.getModule();

        dm.callJNI_OnLoad(emulator);
        SecureNative = vm.resolveClass("com/xunmeng/pinduoduo/secure/SecureNative");
    }

    public static void main(String[] args) {
        Pdd demo = new Pdd();
        demo.nativeGenerate2();
    }

    @Override
    public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/tencent/mars/xlog/PLog->i(Ljava/lang/String;Ljava/lang/String;)V":{
                return;
            }
        }
        super.callStaticVoidMethodV(vm, dvmClass, signature, vaList);
    }

    public void nativeGenerate2(){
        DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
        String result = SecureNative.newObject(null).callJniMethodObject(emulator, "nativeGenerate2", context, "",
                "ChUTbmEXjiUpjF5Q8W40Ag==", "Iqy8ijyU", "/storage/emulated/0", "version=134", 0x17b44082347L).getValue().toString();
        System.out.println("result:"+result);
    }

    @Override
    public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;":{
                String spName = varArg.getObjectArg(0).getValue().toString();
                System.out.println("getSharedPreferences sp Name:"+spName);
                return vm.resolveClass("android/content/SharedPreferences").newObject(spName);
            }
            case "android/content/Context->getSystemService(Ljava/lang/String;)Ljava/lang/Object;": {
                ServiceManager serviceManager = new ServiceManager(vm, signature);
                StringObject serviceName = varArg.getObjectArg(0);
                assert serviceName != null;
                return serviceManager.getService(vm, serviceName.getValue());
            }
            case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
                if((dvmObject.getValue().toString()).equals("pdd_config")){
                    System.out.println("pdd_config表中取值："+varArg.getObjectArg(0).getValue().toString());
                    if (varArg.getObjectArg(0).getValue().equals("secure_sharedpreference_id")) {
                        return new StringObject(vm, "5f41908145dc4f69b8cb6b001071c993");
                    }
                    if (varArg.getObjectArg(0).getValue().equals("secure_mac_address")) {
                        return new StringObject(vm, "F4:60:E2:96:DB:64");
                    }
                }
            }
            case "android/telephony/TelephonyManager->getDeviceId()Ljava/lang/String;": {
                return new StringObject(vm, "");
            }
            case "android/telephony/TelephonyManager->getSubscriberId()Ljava/lang/String;":
                return new StringObject(vm, "");

            case "android/view/WindowManager->getDefaultDisplay()Landroid/view/Display;":
                return vm.resolveClass("android/view/Display").newObject(null);

            case "java/util/Enumeration->nextElement()Ljava/lang/Object;":
                return ((Enumeration) dvmObject).nextElement();

            case "android/content/SharedPreferences->edit()Landroid/content/SharedPreferences$Editor;":{
                return vm.resolveClass("android/content/SharedPreferences$Editor").newObject(null);
            }
            // 这里处理的不好，请参考其余 demo
            case "android/content/SharedPreferences$Editor->putString(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;":{
                return vm.resolveClass("android/content/SharedPreferences$Editor").newObject(null);
            }
            case "android/content/Context->getContentResolver()Landroid/content/ContentResolver;":
                return vm.resolveClass("android/content/ContentResolver").newObject(null);

            case "android/content/Context->registerReceiver(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;":
                return vm.resolveClass("android/content/Intent").newObject(varArg.getObjectArg(1).getValue());

            case "com/android/internal/telephony/ITelephony->getInterfaceDescriptor()Ljava/lang/String;":
                return new StringObject(vm, "");
            // 这里处理的不好，请参考其余 demo
            case "android/content/SharedPreferences$Editor->putInt(Ljava/lang/String;I)Landroid/content/SharedPreferences$Editor;":
                return dvmObject;
            case "java/lang/StringBuilder->append(Ljava/lang/String;)Ljava/lang/StringBuilder;":
                StringBuilder stringBuilder = (StringBuilder) dvmObject.getValue();
                stringBuilder.append(varArg.getObjectArg(0).getValue().toString());
                return dvmObject;

            case "java/lang/StringBuilder->toString()Ljava/lang/String;":{
                StringBuilder stringBuilder1 = (StringBuilder) dvmObject.getValue();
                return new StringObject(vm, stringBuilder1.toString());
            }
        }
        return super.callObjectMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public int callIntMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/content/Context->checkSelfPermission(Ljava/lang/String;)I":{
                String input = varArg.getObjectArg(0).getValue().toString();
                System.out.println("permission name:"+input);
                return 0;
            }
            case "android/telephony/TelephonyManager->getPhoneType()I":
                return 1;
            case "android/telephony/TelephonyManager->getSimState()I":
                return 5;
            case "android/telephony/TelephonyManager->getNetworkType()I":
                return 13;
            case "android/telephony/TelephonyManager->getDataState()I":
                return 2;
            case "android/telephony/TelephonyManager->getDataActivity()I":
                return 4;
        }
        return super.callIntMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/util/UUID->randomUUID()Ljava/util/UUID;":{
                return vm.resolveClass("java/util/UUID").newObject(UUID.randomUUID());
            }
            case "java/net/NetworkInterface->getNetworkInterfaces()Ljava/util/Enumeration;":{
                // 真实情况这个数组要长很多
                String[] NetworkInterfaceNameList = new String[]{"dummy0","r_rmnet_data2","r_rmnet_data3","ip_vti0","wlan0","wlan1"};
                int length = NetworkInterfaceNameList.length;
                List<DvmObject<?>> NetworkInterfacelist = new ArrayList<>();

                for (int i = 0; i < length; i++) {
                    NetworkInterfacelist.add(vm.resolveClass("java/net/NetworkInterface").newObject(NetworkInterfaceNameList[i]));
                }

                return new Enumeration(vm,  NetworkInterfacelist);
            }
            case "com/xunmeng/pinduoduo/service_hook/SystemServiceHooker->getService(Ljava/lang/String;)Landroid/os/IBinder;":{
                return vm.resolveClass("android/os/IBinder").newObject(vaList.getObjectArg(0).getValue().toString());
            }
            case "android/os/Parcel->obtain()Landroid/os/Parcel;":{
                return dvmClass.newObject(null);
            }
            // 这里处理的不好，请参考它篇
            case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;":{
                return new StringObject(vm,"android_id");
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "java/util/UUID->toString()Ljava/lang/String;":{
                UUID uuid = (UUID) dvmObject.getValue();
                return new StringObject(vm, uuid.toString());
            }
            case "java/lang/String->replaceAll(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;": {
                StringObject str = (StringObject) dvmObject;
                StringObject s1 = vaList.getObjectArg(0);
                StringObject s2 = vaList.getObjectArg(1);
                assert s1 != null;
                assert s2 != null;
                return new StringObject(vm, str.getValue().replaceAll(s1.getValue(), s2.getValue()));
            }
            case "android/telephony/TelephonyManager->getDeviceId()Ljava/lang/String;": {
                // 获取deviceid
                return new StringObject(vm, "");
            }
            case "android/telephony/TelephonyManager->getDeviceId(I)Ljava/lang/String;": {
                // 获取deviceid
                return new StringObject(vm, "");
            }
            case "android/telephony/TelephonyManager->getSubscriberId(I)Ljava/lang/String;": {
                return new StringObject(vm, "");
            }
            case "android/telephony/TelephonyManager->getSimSerialNumber()Ljava/lang/String;":
                return new StringObject(vm, "");

            case "android/telephony/TelephonyManager->getSimSerialNumber(I)Ljava/lang/String;":
                return new StringObject(vm, "");

            case "java/net/NetworkInterface->getName()Ljava/lang/String;":{
                return new StringObject(vm, dvmObject.getValue().toString());
            }
            case "java/net/NetworkInterface->getHardwareAddress()[B":
                byte[] result = string2bytes("F460E296DB64");
                return new ByteArray(vm, result);

            case "android/content/Intent->getExtras()Landroid/os/Bundle;":{
                return vm.resolveClass("android/os/Bundle").newObject(signature);
            }
            case "android/os/Bundle->keySet()Ljava/util/Set;":
                String[] s = new String[]{"technology", "icon-small", "max_charging_voltage",
                        "health","max_charging_current", "status","plugged", "present", "seq",
                        "charge_counter", "level", "scale", "temperature", "voltage",
                        "invalid_charger","battery_low"};
                Set<String> set = new HashSet<>(Arrays.asList(s));
                return vm.resolveClass("java/util/Set").newObject(set);

            case "android/os/Bundle->get(Ljava/lang/String;)Ljava/lang/Object;":{
                String key = vaList.getObjectArg(0).getValue().toString();
                System.out.println("this key:"+key);
                switch (key){
                    case "technology":{
                        return vm.resolveClass("java/lang/Object").newObject("Li-poly");
                    }
                    case "icon-small":{
                        return vm.resolveClass("java/lang/Object").newObject("17303585");
                    }
                    case "max_charging_voltage":{
                        return vm.resolveClass("java/lang/Object").newObject("5000000");
                    }
                    case "health":{
                        return vm.resolveClass("java/lang/Object").newObject("2");
                    }
                    case "max_charging_current":{
                        return vm.resolveClass("java/lang/Object").newObject("1500000");
                    }
                    case "status":{
                        return vm.resolveClass("java/lang/Object").newObject("2");
                    }
                    case "plugged":{
                        return vm.resolveClass("java/lang/Object").newObject("1");
                    }
                    case "present":{
                        return vm.resolveClass("java/lang/Object").newObject("true");
                    }
                    case "seq":{
                        return vm.resolveClass("java/lang/Object").newObject("287");
                    }
                    case "charge_counter":{
                        return vm.resolveClass("java/lang/Object").newObject("1496324");
                    }
                    case "level":{
                        return vm.resolveClass("java/lang/Object").newObject("71");
                    }
                    case "scale":{
                        return vm.resolveClass("java/lang/Object").newObject("100");
                    }
                    case "temperature":{
                        return vm.resolveClass("java/lang/Object").newObject("390");
                    }
                    case "voltage":{
                        return vm.resolveClass("java/lang/Object").newObject("4193");
                    }
                    case "invalid_charger":{
                        return vm.resolveClass("java/lang/Object").newObject("0");
                    }
                    case "battery_low":{
                        return vm.resolveClass("java/lang/Object").newObject("false");
                    }
                }
                break;
            }
            case "java/lang/Object->toString()Ljava/lang/String;":{
                return new StringObject(vm, dvmObject.getValue().toString());
            }
            case "java/lang/Object->getClass()Ljava/lang/Class;":{
                return dvmObject.getObjectType();
            }
            case "java/lang/Class->getEnclosingClass()Ljava/lang/Class;":{
                return vm.resolveClass("com/android/internal/telephony/IPhoneSubInfo$Stub");
            }
            case "android/os/Parcel->readString()Ljava/lang/String;":{
                return new StringObject(vm, "");
            }
            case "java/lang/String->trim()Ljava/lang/String;":{
                String str = dvmObject.getValue().toString();
                return new StringObject(vm, str.trim());
            }
            case "android/net/ConnectivityManager->getActiveNetworkInfo()Landroid/net/NetworkInfo;":{
                return vm.resolveClass("android/net/NetworkInfo").newObject(null);
            }
            case "android/app/ActivityThread->getApplication()Landroid/app/Application;":{
                return vm.resolveClass("android/app/Application", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
            }
            case "android/content/Context->getContentResolver()Landroid/content/ContentResolver;":{
                return vm.resolveClass("android/content/ContentResolver").newObject(signature);
            }
            case "java/util/Set->toArray()[Ljava/lang/Object;":{
                return ProxyDvmObject.createObject(vm, ((Set<?>) dvmObject.getValue()).toArray());
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    private byte[] string2bytes(String str) {
        int length = str.length();
        byte[] bArr = new byte[(length / 2)];
        for (int i = 0; i < length; i += 2) {
            bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16));
        }
        return bArr;
    }

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature) {
            case "android/telephony/TelephonyManager->PHONE_TYPE_GSM:I":
                return 1;
            case "com/android/internal/telephony/IPhoneSubInfo$Stub->TRANSACTION_getDeviceId:I":
                return 142;
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "android/content/Context->WINDOW_SERVICE:Ljava/lang/String;":{
                return new StringObject(vm, "window");
            }
            case "android/provider/Settings$Secure->ANDROID_ID:Ljava/lang/String;":{
                return new StringObject(vm, "android_id");
            }
            case "android/os/Build->SERIAL:Ljava/lang/String;":{
                return new StringObject(vm, "unknown");
            }
            // android.intent.action.BATTERY_CHANGED
            case "android/content/Intent->ACTION_BATTERY_CHANGED:Ljava/lang/String;":{
                return new StringObject(vm, "android.intent.action.BATTERY_CHANGED");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            case "android/util/DisplayMetrics-><init>()V":{
                return dvmClass.newObject(signature);
            }
            case "android/content/IntentFilter-><init>(Ljava/lang/String;)V":{
                return dvmClass.newObject(varArg.getObjectArg(0).getValue().toString());
            }
            case "java/lang/StringBuilder-><init>()V":{
                return vm.resolveClass("java/lang/StringBuilder").newObject(new StringBuilder());
            }
        }
        return super.newObject(vm, dvmClass, signature, varArg);
    }

    @Override
    public void callVoidMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "android/view/Display->getRealMetrics(Landroid/util/DisplayMetrics;)V":{
                return;
            }
            case "android/content/SharedPreferences$Editor->apply()V":{
                return;
            }
        }
        super.callVoidMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/util/DisplayMetrics->densityDpi:I":{
                return 440;
            }
            case "android/util/DisplayMetrics->widthPixels:I":{
                return 1080;
            }
            case "android/util/DisplayMetrics->heightPixels:I":{
                return 2160;
            }
        }
        return super.getIntField(vm, dvmObject, signature);
    }

    @Override
    public boolean callBooleanMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {
        switch (signature){
            case "java/util/Enumeration->hasMoreElements()Z":{
                return ((Enumeration) dvmObject).hasMoreElements();
            }
            case "android/os/IBinder->transact(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z":{
                return true;
            }
        }
        return super.callBooleanMethod(vm, dvmObject, signature, varArg);
    }

    @Override
    public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
        switch (signature){
            case "android/provider/Settings$Secure->getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String;":{
                if(varArg.getObjectArg(1).getValue().equals("android_id")){
                    return new StringObject(vm, "63b489ab3358dd54");
                }
            }
            case "com/android/internal/telephony/ITelephony$Stub->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ITelephony;":{
                return vm.resolveClass("com/android/internal/telephony/ITelephony", vm.resolveClass("java/lang/Object")).newObject(signature);
            }
        }
        return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
    }

    @Override
    public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/os/Parcel->writeInterfaceToken(Ljava/lang/String;)V":{
                return;
            }
            case "android/os/Parcel->writeString(Ljava/lang/String;)V":{
                return;
            }
            case "android/os/Parcel->readException()V":{
                return;
            }
            case "android/os/Parcel->recycle()V":{
                return;
            }
        }
        super.callVoidMethodV(vm, dvmObject, signature, vaList);
    }


    @Override
    public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "com/xunmeng/pinduoduo/ut/util/Utils->hitSpng(Landroid/content/Context;)Z":{
                return false;
            }
        }
        return super.callStaticBooleanMethodV(vm, dvmClass, signature, vaList);
    }

    @Override
    public long getLongField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            case "android/content/pm/PackageInfo->firstInstallTime:J":{
                return 1628930489657L;
            }
            case "android/content/pm/PackageInfo->lastUpdateTime:J":{
                return 1628930489657L;
            }
        }
        return super.getLongField(vm, dvmObject, signature);
    }

    @Override
    public FileResult<?> resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("file open:"+pathname);
        if(pathname.equals("/proc/cpuinfo")){
            return FileResult.success(new SimpleFileIO(oflags, new File("unidbg-android/src/test/resources/demo10/proc/cpuinfo"), pathname));
        }
        return null;
    }

    @Override
    public boolean callBooleanMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/net/NetworkInfo->isConnected()Z":{
                return false;
            }
            case "java/lang/String->startsWith(Ljava/lang/String;)Z":{
                String str = (String) dvmObject.getValue();
                return str.startsWith(vaList.getObjectArg(0).getValue().toString());
            }
        }
        return super.callBooleanMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/net/NetworkInfo->getType()I":{
                return 0;
            }
            case "android/content/SharedPreferences->getInt(Ljava/lang/String;I)I":{
                return 7;
            }
        }
        return super.callIntMethodV(vm, dvmObject, signature, vaList);
    }
}
